package aurora

import (
	"errors"
	"github.com/aurora-go/aurora/utils"
	"github.com/iancoleman/strcase"
	jsoniter "github.com/json-iterator/go"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"strings"
)

/*
	controller.go 用于设计 反射注入的处理器
	aurora的参数注入规则:
	URL  参数永远排列在参数列表的最前,
	GET  参数跟随在URL之后
	POST 排序在最后

	基于go反射的特点，处理器传递参数和前端传递参数的名称没有任何关系，只和顺序有关(调用服务器接口就和调用函数传参一样，需要给对对应类型)

*/
const (
	web_request       = "*http.Request"
	web_response      = "http.ResponseWriter"
	web_multipartfile = "*aurora.MultipartFile"
	web_ctx           = "aurora.Ctx"
)

// intrinsic 用于存储Aurora框架内部的控制器参数属性，k/v 存储的是 通过反射可以获得的类型名称，用于校验在调用时期参数是否是框架内部参数，如果是则把内部参数直接注入
var intrinsic = map[string]string{
	web_request:       "*http.Request",
	web_response:      "http.ResponseWriter",
	web_multipartfile: "*aurora.MultipartFile",
	web_ctx:           "aurora.Ctx",
}

// control 用于存储在服务器启动之前注册的接口信息，需要在加载完配置项之后进行统一注册
type controlInfo struct {
	path       string
	control    Controller
	middleware []Middleware
}

type controller struct {
	//解析的文件部分
	File *MultipartFile
	//上下文数据
	ctx Ctx
	p   *proxy
	//路径参数,按顺序依次
	UrlVariable []string
	//入参参数个数
	InNum int
	//返回值个数
	OutNum int
	//按顺序存储每个入参的反射实例
	InvokeValues []reflect.Value
	//参数赋值序列表
	Args []string
	//可赋值参数索引序列
	AssignmentIndex []int
	//返回参数实例
	ReturnValues []reflect.Value
	//将被调用的函数,注册阶段已经被构建成为反射类型
	Fun     reflect.Value
	FunType reflect.Type
}

// InitArgs 初始化参数信息，注册函数阶段调用
func (c *controller) InitArgs() {
	c.InNum = c.FunType.NumIn()
	c.OutNum = c.FunType.NumOut()
	c.AssignmentIndex = make([]int, 0)
	//初始化参数列表
	if c.InNum > 0 {
		c.InvokeValues = make([]reflect.Value, c.InNum)
		c.Args = make([]string, c.InNum)
	}
	for i := 0; i < c.InNum; i++ {
		arguments := c.FunType.In(i)
		//初始化参数期间对Get参数列表进行标记，以便匹配参数顺序,此处主要是处理存在web请求体或者响应体的位置
		key := arguments.String()
		if v, b := intrinsic[key]; b {
			c.Args[i] = v
			c.InvokeValues[i] = reflect.New(arguments).Elem()
			continue
		}
		value := reflect.New(arguments).Elem()
		//对非内部参数进行 字段校验 存在为导出字段需要更改
		if arguments.Kind() == reflect.Struct || arguments.Kind() == reflect.Pointer {
			if !checkArguments(value) {
				//检查存在 未导出字段
				log.Fatalln("The index: ", i, " parameter is checked to exist as an export field, please check the field permission")
			}
		}
		c.InvokeValues[i] = value
		//初始化可赋值参数序列，存储可赋值的索引
		c.AssignmentIndex = append(c.AssignmentIndex, i)
	}
}

// checkArguments 校验接口入参 参数所有字段是否为导出字段
// 找要有一个是非导出字段则返回 false
func checkArguments(s reflect.Value) bool {
	var v reflect.Value
	if s.Kind() != reflect.Struct && s.Kind() != reflect.Pointer {
		return true
	}
	//如果入参是指针
	if s.Kind() == reflect.Pointer {
		//校验入参 此刻的指针数据是未初始化情况 需要分配一个值来进行校验,分配的值仅用于校验
		elem := reflect.New(s.Type().Elem()).Elem()
		return checkArguments(elem)
	} else {
		v = s
	}
	st := v.Type()
	for i := 0; i < st.NumField(); i++ {
		field := st.Field(i)
		// 校验当前结构体的字段是否是导出状态
		if !field.IsExported() {
			return false
		}
		//对该字段进行递归检查
		if !checkArguments(v.Field(i)) {
			return false
		}
	}
	return true
}

// invoke 接口调用
func (c *controller) invoke() []reflect.Value {
	//before
	r := c.Fun.Call(c.InvokeValues)
	//after
	return r
}

//入参解析
// var values []string 用于接收 参数列表，该列表顺序规则为(rest full URL参数永远放在最前):
// values:   [rest ful路径参数,GET 请求参数,POST请求体参数]
func (c *controller) analysisInput(request *http.Request, response http.ResponseWriter, ctx Ctx) {
	var values []string
	//根据 请求类型初始化 values 列表
	switch request.Method {
	case http.MethodGet:
		values = getRequest(request, c)
	case http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodHead:
		values = getRequest(request, c)
		post := postRequest(request, c)
		values = append(values, post...)
	}

	if values == nil {
		//如果 values 在前面的请求类型中均未被初始化，则初始化 values为空零元素切片,以防下面出现空指针错误
		values = make([]string, 0)
	}
	//如果get请求参数个数少于处理函数入参数量，则按照get参数个数初始化，剩余部分判断为空，或是web请求提参数
	l := 0
	//校验 传递参数的数量和 可输入的数量
	if len(c.AssignmentIndex) <= len(values) {
		//传递参数的数量大于 处理函数可赋值数量，则默认丢弃多余部分的参数，以可赋值参数长度为主
		l = len(c.AssignmentIndex)
	} else {
		//此情况 传入的参数小于可赋值参数数量，把传入的参数按照可赋值索引依次赋值
		l = len(values)
	}
	//初始化参数列表，如果 values 为零个元素 则 不会给Args入参 进行初始化
	for i := 0; i < l; i++ {
		v := ""
		if strings.Contains(values[i], "=") {
			vs := strings.Split(values[i], "=")
			v = vs[1]
		} else {
			v = values[i]
		}
		assig := c.AssignmentIndex[i]
		c.Args[assig] = v
	}
	//开始初始化参数注入，Args中的参数没有被初始化 依然为 "" 空字符串，则在初始化的时候默认为 零值
	for i := 0; i < c.InNum; i++ {
		v := c.Args[i]
		if v == "" {
			continue
		}
		// 内部参数设计，还可以改进
		if _, b := intrinsic[v]; b { //通过 map来判断参数是否为内部参数
			if v == web_request {
				c.InvokeValues[i] = reflect.ValueOf(request)
				continue
			}
			if v == web_response {
				c.InvokeValues[i] = reflect.ValueOf(response)
				continue
			}
			if v == web_multipartfile {
				c.InvokeValues[i] = reflect.ValueOf(c.File)
				continue
			}
			if v == web_ctx {
				c.InvokeValues[i] = reflect.ValueOf(ctx)
				continue
			}
		}
		value := c.InvokeValues[i]
		json := jsoniter.ConfigCompatibleWithStandardLibrary
		var data interface{}
		var err error
		if request.Method == http.MethodPost || request.Method == http.MethodPut {
			err = json.Unmarshal([]byte(v), &data)
			if err != nil {
				panic("The json parameter decoding failed, please check whether the json data format is correct.error:" + err.Error())
			}
		} else {
			data = v
		}
		//starAssignment(value, data)
		err = utils.StarAssignment(value, data)
		if err != nil {
			panic(err)
		}
	}

}

func getRequest(request *http.Request, c *controller) []string {
	values := make([]string, 0)
	url := request.RequestURI
	//解析存在get参数
	if index := strings.Index(url, "?"); index != -1 {
		url = url[index+1:]
		if c.UrlVariable != nil {
			//如果存在路径参数,我们把路径参数附加在 get参数之后
			values = c.UrlVariable
		}
		value := strings.Split(url, "&")
		values = append(values, value...)
	} else {
		if c.UrlVariable != nil {
			//如果存在路径参数,我们把路径参数附加在 get参数之后
			values = c.UrlVariable
		}
	}
	return values
}

func postRequest(request *http.Request, c *controller) []string {
	values := make([]string, 0)
	//处理文件上传处理 该处理操作在 中间件阶段可能被执行，两种情况同时出现的情况未测试，可能出现bug
	request.ParseMultipartForm(c.p.MaxMultipartMemory)
	form := request.MultipartForm
	if form != nil {
		if form.File != nil {
			//封装解析好的 文件部分
			c.File = &MultipartFile{File: form.File}
		}

		if form.Value != nil {
			// 2022-5-20 更新 多文本混合上传方式
			for _, v := range form.Value {
				vlen := len(v)
				if vlen == 0 {
					continue
				}
				values = append(values, v[0])
			}
			return values
		}
	}
	//非文件上传处理
	if request.Body != nil {
		all, err := ioutil.ReadAll(request.Body)
		if err != nil {
			//待处理
		}
		//确保读取到内容
		if all != nil && len(all) > 0 {
			values = append(values, string(all))
		}
	}
	return values
}

// starAssignment
// data 传入 value 对应的 map[string]interface{}
func starAssignment(value reflect.Value, data interface{}) {
	switch value.Kind() {
	case reflect.Slice, reflect.Map, reflect.Struct:
		assignment(value, data)
	case reflect.Pointer:
		//需要先分配一个对应类型反射的值，这个值调用 Elem 获取对应指向的值才不会为空
		d := reflect.New(value.Type().Elem())
		//传递 指针指向的值，进行参数注入
		assignment(d.Elem(), data)
		//把分配的反射指针设置给参数
		value.Set(d)
	default:
		assignment(value, data)
	}
}

// assignment 递归对单个反射结构体进行赋值
// arguments 带注入的参数,入参必须是值类型，指针类型赋值需要传递指针所指向的value，带注入的参数为map类型时候 arguments发射的map对应的接收类型必须是 map[string]interface{}
// value对应注入的 k/v
func assignment(arguments reflect.Value, value interface{}) {
	if value == nil || arguments.Type() == nil {
		return
	}
	var FieldName map[string]string
	//获取反射的类型
	t := arguments.Type()
	if t.Kind() == reflect.Struct {
		FieldName = make(map[string]string)
		//如果参数是结构体，初始化一份 字段名的对应表,该对应表，适配一些命名规范 蛇形,下划线什么的
		for i := 0; i < t.NumField(); i++ {
			f := t.Field(i)
			snake := strcase.ToSnake(f.Name)
			camel := strcase.ToCamel(f.Name)
			kebab := strcase.ToKebab(f.Name)
			lowerCamel := strcase.ToLowerCamel(f.Name)
			FieldName[snake] = f.Name
			FieldName[camel] = f.Name
			FieldName[kebab] = f.Name
			FieldName[lowerCamel] = f.Name
			//支持标签定义，以防出现 strcase 库不稳定的时候 通过json来自定义
			if s, b := f.Tag.Lookup("json"); b {
				if s != "" {
					FieldName[s] = f.Name
				}
			}
		}
	}
	switch value.(type) {
	//如果参数为结构体，value对应为map 类型
	case map[string]interface{}:
		values := value.(map[string]interface{})
		if t.Kind() == reflect.Map {
			//如果 此处的 类型是map 则在此处就可以直接给 map 进行赋值 直接把 values 赋值给 value
			// 为什么在此处判断 map类型 ，避免走入下面的结构体解析逻辑中,这个赋值解析是以json的方式去对应 结构体 map[string]interface{} 在这个地方和结构体会有歧义
			//判断 map存储的值类型 是interface{} 就直接赋值
			// 此处进行修复 支持具体结构体，但是key的值必须是个字符串普通 key,请勿通过结构体形式作为key接收
			// 结构体形式的key，需要后续看情况。
			makeMap := reflect.MakeMap(arguments.Type())
			assignmentMap(makeMap, values)
			arguments.Set(makeMap)
			return
		}
		//走到此处这个 arguments 必然是个结构体
		for k, v := range values {
			var field reflect.Value
			//判断结构体类型，及其校验结构体字段名对应表是否初始化
			if (t.Kind() == reflect.Struct) && FieldName != nil {
				if name, b := FieldName[k]; b {
					field = arguments.FieldByName(name)
				} else {
					//此处如果没有找到对应的字段名称，说明传递的json参数无法解析注入到 参数列表中
					//直接给前端返回错误提示，或者输出日志,此处直接日志返回，执行到具体处理器上参数将为零值
					return
				}
			} else {
				//校验结构体失败此处要么返回错误要么 panic，或者 return 放弃这个字段的初始化，处理器将会接收到零值
				return
			}
			if field.Type() == nil || v == nil { //v == nil 防止下面的 switch 走到 default中的 case reflect.Pointer 造成栈溢出
				return
			}
			switch v.(type) {
			case map[string]interface{}:
				//处理结构体类型字段
				if field.Kind() == reflect.Pointer {
					//指针类型，必须先分配内存,field 的类型为某个结构体的指针，先要获取到该结构体指针类型，指针指向的具体类型，然后为其分配New，New得到的才是 想要的指针类型
					v2 := reflect.New(field.Type().Elem())
					//获取指针的值，初始化复制先要拿到指针指向的值才可操作
					elem := v2.Elem()
					//初始化赋值
					assignment(elem, v)
					field.Set(v2)
				} else {
					assignment(field, v)
				}
			default:
				//处理普通字段属性
				assignment(field, v)
			}
		}
	//处理单个变量,结构体在上面的case 中应该已经被处理了
	default:
		//此处单一字段的基本类型转化，还是不能直接断言赋值，可能存在 传递json中的数字类型就是字符串形式，json解析为map的时候
		switch arguments.Kind() {
		case reflect.String:
			variable := value.(string)
			arguments.SetString(variable)
		case reflect.Int:
			var variable int64
			switch value.(type) {
			case string:
				atoi, err := strconv.Atoi(value.(string))
				if err != nil {
					e := ArgsAnalysisError{}
					e.s = "The reflection target parameter is of type int. The parameter you gave cannot be converted to type int. Please check the json format of the passed parameter.error:" + err.Error()
					panic(e)
				}
				variable = int64(atoi)
			case float64:
				variable = int64(value.(float64))
			case int:
				variable = int64(value.(int))
			}
			arguments.SetInt(variable)
		case reflect.Float64:
			var variable float64
			switch value.(type) {
			case string:
				float, err := strconv.ParseFloat(value.(string), 64)
				if err != nil {
					e := ArgsAnalysisError{}
					e.s = "The reflection target parameter is of type float64. The parameter you gave cannot be converted to type float64. Please check the json format of the passed parameter.error:" + err.Error()
					panic(e)
				}
				variable = float
			case float64:
				variable = value.(float64)
			}
			arguments.SetFloat(variable)
		case reflect.Bool:
			var variable bool
			switch value.(type) {
			case string:
				parseBool, err := strconv.ParseBool(value.(string))
				if err != nil {
					e := ArgsAnalysisError{}
					e.s = "The reflection target parameter is of type bool. The parameter you gave cannot be converted to type bool. Please check the json format of the passed parameter.error:" + err.Error()
					panic(e)
				}
				variable = parseBool
			case bool:
				variable = value.(bool)
			}
			arguments.SetBool(variable)
		case reflect.Map:
			//通过前面的 case 内层的  reflect.Map 无法进入到这里

			//反射确定 value类型
		case reflect.Pointer:
			typ := arguments.Type().Elem()
			v := reflect.New(typ)
			elem := v.Elem()
			assignment(elem, value)
			arguments.Set(v)
		case reflect.Slice:
			elem := arguments.Type().Elem()
			slice := reflect.MakeSlice(arguments.Type(), 0, 0)
			arr, b := value.([]interface{})
			if !b {
				e := ArgsAnalysisError{}
				e.s = "The reflection target parameter is of type slice. The parameter you gave cannot be converted to type slice. Please check the json format of the passed parameter."
				panic(e)
			}
			switch elem.Kind() {
			case reflect.Int:
				for _, element := range arr {
					var v int
					switch element.(type) {
					case string:
						atoi, err := strconv.Atoi(value.(string))
						if err != nil {
							e := ArgsAnalysisError{}
							e.s = "The reflection target parameter is of type int. The parameter you gave cannot be converted to type int. Please check the json format of the passed parameter.error:" + err.Error()
							panic(e)
						}
						v = atoi
					case float64:
						e := element.(float64)
						v = int(e)
					}
					slice = reflect.Append(slice, reflect.ValueOf(v))
				}
				arguments.Set(slice)
			case reflect.Float64:
				for _, element := range arr {
					var v float64
					switch element.(type) {
					case string:
						float, err := strconv.ParseFloat(element.(string), 64)
						if err != nil {
							e := ArgsAnalysisError{}
							e.s = "The reflection target parameter is of type float64. The parameter you gave cannot be converted to type float64. Please check the json format of the passed parameter.error:" + err.Error()
							panic(e)
						}
						v = float
					case float64:
						v = element.(float64)
					}
					slice = reflect.Append(slice, reflect.ValueOf(v))
				}
				arguments.Set(slice)
			case reflect.String:
				for _, element := range arr {
					e := element.(string)
					slice = reflect.Append(slice, reflect.ValueOf(e))
				}
				arguments.Set(slice)
			case reflect.Bool:
				for _, element := range arr {
					var bl bool
					switch element.(type) {
					case string:
						parseBool, err := strconv.ParseBool(element.(string))
						if err != nil {
							e := ArgsAnalysisError{}
							e.s = "The reflection target parameter is of type bool. The parameter you gave cannot be converted to type bool. Please check the json format of the passed parameter.error:" + err.Error()
							panic(e)
						}
						bl = parseBool
					case bool:
						bl = element.(bool)
					}
					slice = reflect.Append(slice, reflect.ValueOf(bl))
				}
				arguments.Set(slice)
			case reflect.Struct:
				for _, element := range arr {
					v := reflect.New(elem)
					v2 := v.Elem()
					assignment(v2, element)
					slice = reflect.Append(slice, v2)
				}
				arguments.Set(slice)
			case reflect.Pointer:
				for _, element := range arr {
					v := reflect.New(elem.Elem())
					v2 := v.Elem()
					assignment(v2, element)
					slice = reflect.Append(slice, v)
				}
				arguments.Set(slice)
			}
		}
	}
}

// assignmentMap 专门针对 map数据类型 进行解析
func assignmentMap(arguments reflect.Value, value map[string]interface{}) {
	if value == nil || arguments.Type() == nil {
		return
	}
	t := arguments.Type()
	//makeMap := reflect.MakeMap(t)
	//t.Elem() 获取map 存储的value类型
	switch t.Elem().Kind() {
	//检测 map存储的具体类型
	case reflect.Interface:
		//检测操map 存储value类型为接口，json解码的map刚好对应，所以可以直接通过反射赋值
		if t.Elem().Kind().String() == "interface" {
			of := reflect.ValueOf(value)
			arguments.Set(of)
			return
		}
	case reflect.Pointer:
		//map 存储的数据为指针,通过反射获取到的类型也是指针，在分配内存的时候不能分配指针类型，要分配指针指向的类型才是正确结果
		for k, v := range value {
			key := reflect.New(t.Key()).Elem()
			assignment(key, k)
			v2 := reflect.New(t.Elem().Elem())
			val := v2.Elem()
			assignment(val, v)
			arguments.SetMapIndex(key, v2)
		}
		return
	case reflect.Int, reflect.Float64, reflect.String, reflect.Bool, reflect.Slice, reflect.Struct:
		for k, v := range value {
			key := reflect.New(t.Key()).Elem()
			assignment(key, k)
			val := reflect.New(t.Elem()).Elem()
			assignment(val, v)
			arguments.SetMapIndex(key, val)
		}
		return
	}
}

// Control 初始化装配结构体依赖 control 参数必须是指针
func (a *Aurora) control(control Controller) {
	value, err := checkControl(control)
	if err != nil {
		a.Error(err.Error())
		return
	}
	if a.controllers == nil {
		a.controllers = make([]*reflect.Value, 0)
	}
	a.controllers = append(a.controllers, value)
}

// checkControl 校验处理器的规范形式
func checkControl(control Controller) (*reflect.Value, error) {
	v := reflect.ValueOf(control)
	//指针类型校验
	if v.Kind() != reflect.Pointer {
		return nil, errors.New("requires a pointer parameter")
	}
	//空指针校验
	if v.IsNil() {
		return nil, errors.New("null pointer")
	}
	//指针类型结构体校验
	if v.Elem().Kind() != reflect.Struct {
		return nil, errors.New("requires a struct type")
	}
	return &v, nil
}

const ref = "ref"

// dependencyInjection Control 依赖加载
func (a *Aurora) dependencyInjection() {
	if a.controllers == nil {
		return
	}
	l := len(a.controllers)
	for i := 0; i < l; i++ {
		control := *a.controllers[i]
		if control.Kind() == reflect.Pointer {
			control = control.Elem()
		}
		for j := 0; j < control.NumField(); j++ {
			field := control.Type().Field(j)
			//查找结构体字段的tag 找到 ref属性并初始化
			if r, b := field.Tag.Lookup(ref); b {
				if r == "" {
					//暂时选择警告的方式
					a.Warn("ref tag value is ''")
					continue
				}
				//校验字段是否可操作
				if !field.IsExported() {
					a.Error(control.Type().String(), " '", field.Name, "' field .Not an exported field, ref attribute injection failed")
					os.Exit(-1)
				}
				value := a.component.get(r)
				if value == nil {
					//若是 容器中未找到 ref 则初始化失败 结束程序
					a.Error("not found ref: " + r)
					os.Exit(-1)
				}
				//容器中找到了 ref 属性的 参数，把该参数赋值给 给定的处理器字段
				if err := utils.Injection(control.Field(j), *value); err != nil {
					//初始化赋值阶段错误
					a.Error(err.Error())
					a.Panic(err.Error())
				}
			}
			//查询 value 属性 读取config中的基本属性
			if v, b := field.Tag.Lookup("value"); b {
				if v == "" {
					a.Warn("value tag value is ''")
					continue
				}
				get := a.config.Get(v)
				if get == nil {
					//如果查找结果大小等于0 则表示不存在
					continue
				}
				//把查询到的 value 初始化给指定字段
				starAssignment(control.Field(j), get)
			}
		}
	}
}
